diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:56 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:56 +0000 |
| commit | 994defd6446ce20c4b4e0d6cc91688b0e64230a4 (patch) | |
| tree | 750bc8a9c7a16908a8e2f01761ab087a72520cff /app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx | |
| parent | aa86729f9a2ab95346a2851e3837de1c367aae17 (diff) | |
(최겸) 기술영업 파트너 페이지 작업사항
Diffstat (limited to 'app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx')
| -rw-r--r-- | app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx | 179 |
1 files changed, 169 insertions, 10 deletions
diff --git a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx index 40be6773..b9c957f0 100644 --- a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx +++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx @@ -1,17 +1,176 @@ +import * as React from "react"; +import Link from "next/link"; +import { Metadata } from "next"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { LogIn } from "lucide-react"; import { Shell } from "@/components/shell"; +import { + TECH_SALES_QUOTATION_STATUSES, + TECH_SALES_QUOTATION_STATUS_CONFIG +} from "@/db/schema"; + +import { getQuotationStatusCounts } from "@/lib/techsales-rfq/service"; +import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table"; + +export const metadata: Metadata = { + title: "기술영업 해양TOP 견적서 관리", + description: "기술영업 해양TOP RFQ 견적서를 관리합니다.", +}; + +export default async function VendorQuotationsTopPage() { + // 세션 확인 + const session = await getServerSession(authOptions); + + if (!session?.user) { + return ( + <Shell> + <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4"> + <div className="text-center"> + <h2 className="text-2xl font-bold tracking-tight">로그인이 필요합니다</h2> + <p className="text-muted-foreground"> + 견적서를 확인하려면 로그인해주세요. + </p> + </div> + <Button asChild> + <Link href="/api/auth/signin"> + <LogIn className="mr-2 h-4 w-4" /> + 로그인 + </Link> + </Button> + </div> + </Shell> + ); + } + + // 벤더 ID 확인 (사용자의 회사 ID가 벤더 ID) + const vendorId = session.user.techCompanyId; + if (!vendorId) { + return ( + <Shell> + <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4"> + <div className="text-center"> + <h2 className="text-2xl font-bold tracking-tight">기술영업 벤더 정보가 없습니다</h2> + <p className="text-muted-foreground"> + 기술영업 벤더 정보가 없습니다. 관리자에게 문의하세요. + </p> + </div> + </div> + </Shell> + ); + } + + // 견적서 상태별 개수 조회 + const statusCountsPromise = getQuotationStatusCounts(vendorId.toString(), "TOP"); -export default function TechSalesRfqShipPage() { return ( - <Shell className="gap-6"> - <div> - <h1 className="text-2xl font-bold">기술영업 - 해양 Hull/Top RFQ</h1> - <p className="text-muted-foreground"> - 벤더가 해양 Hull/Top RFQ 목록을 확인하고 관리합니다. - </p> - <p className="text-muted-foreground"> - 기술영업 해양 Hull/Top 은 업무 요구사항이 동일하다면 통합으로 개발될 수 있습니다. - </p> + <Shell variant="fullscreen" className="h-full"> + {/* 고정 헤더 영역 */} + <div className="flex-shrink-0"> + <div className="flex-shrink-0 flex flex-col gap-4 md:flex-row md:items-center md:justify-between"> + <div> + <h1 className="text-3xl font-bold tracking-tight">기술영업 해양TOP 견적서</h1> + <p className="text-muted-foreground"> + 할당받은 해양TOP RFQ에 대한 견적서를 작성하고 관리합니다. + </p> + </div> + </div> + + {/* 상태별 개수 카드 */} + <div className="flex-shrink-0"> + <React.Suspense + fallback={ + <div className="w-full overflow-x-auto"> + <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit"> + {Array.from({ length: 5 }).map((_, i) => ( + <Card key={i} className="min-w-[160px]"> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium truncate">로딩중...</CardTitle> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold">-</div> + </CardContent> + </Card> + ))} + </div> + </div> + } + > + <StatusCards statusCountsPromise={statusCountsPromise} /> + </React.Suspense> + </div> + + {/* 견적서 테이블 */} + <div className="flex-1 min-h-0 overflow-hidden"> + <div className="h-full overflow-auto"> + <VendorQuotationsTable vendorId={vendorId.toString()} rfqType="TOP" /> + </div> + </div> </div> </Shell> ); +} + +// 상태별 개수 카드 컴포넌트 +async function StatusCards({ + statusCountsPromise, +}: { + statusCountsPromise: Promise<{ + data: { status: string; count: number }[] | null; + error: string | null; + }>; +}) { + const { data: statusCounts, error } = await statusCountsPromise; + + if (error || !statusCounts) { + return ( + <div className="w-full overflow-x-auto"> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 min-w-fit"> + <Card className="min-w-[160px]"> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium truncate">오류</CardTitle> + </CardHeader> + <CardContent> + <div className="text-2xl font-bold text-red-600">-</div> + <p className="text-xs text-muted-foreground truncate"> + 데이터를 불러올 수 없습니다 + </p> + </CardContent> + </Card> + </div> + </div> + ); + } + + // 중앙화된 상태 설정 사용 + const statusEntries = Object.entries(TECH_SALES_QUOTATION_STATUSES).map(([, statusValue]) => ({ + key: statusValue, + ...TECH_SALES_QUOTATION_STATUS_CONFIG[statusValue] + })); + + console.log(statusCounts, "statusCounts") + + return ( + <div className="w-full overflow-x-auto"> + <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit"> + {statusEntries.map((status) => ( + <Card key={status.key} className="min-w-[160px]"> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium truncate">{status.label}</CardTitle> + </CardHeader> + <CardContent> + <div className={`text-2xl font-bold ${status.color}`}> + {statusCounts.find(item => item.status === status.key)?.count || 0} + </div> + <p className="text-xs text-muted-foreground truncate"> + {status.description} + </p> + </CardContent> + </Card> + ))} + </div> + </div> + ); }
\ No newline at end of file |
